use iri_string::spec::IriSpec;
use iri_string::types::RiReferenceString;
use std::collections::BTreeMap;
use url::Url;
pub type Id = String;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ParamStyle {
Plain,
Matrix,
Query,
Header,
Template,
}
#[derive(Debug)]
pub struct Application {
pub resources: Vec<Resources>,
pub resource_types: Vec<ResourceType>,
pub docs: Vec<Doc>,
pub grammars: Vec<Grammar>,
pub representations: Vec<RepresentationDef>,
}
impl Application {
pub fn get_resource_type_by_id(&self, id: &str) -> Option<&ResourceType> {
self.resource_types.iter().find(|rt| id == rt.id.as_str())
}
pub fn get_resource_type_by_href(&self, href: &Url) -> Option<&ResourceType> {
if let Some(fragment) = href.fragment() {
self.get_resource_type_by_id(fragment)
} else {
None
}
}
pub fn iter_resources(&self) -> impl Iterator<Item = (Option<Url>, &Resource)> {
self.resources
.iter()
.flat_map(|rs| rs.resources.iter().map(|r| (r.url(rs.base.as_ref()), r)))
}
pub fn get_resource_by_href(&self, href: &Url) -> Option<&Resource> {
self.iter_resources()
.find(|(url, _)| {
if let Some(url) = url {
url == href
} else {
false
}
})
.map(|(_, r)| r)
}
pub fn iter_referenced_types(&self) -> impl Iterator<Item = String> + '_ {
self.iter_resources()
.flat_map(|(_u, r)| r.iter_referenced_types().map(|s| s.to_string()))
.chain(
self.resource_types
.iter()
.flat_map(|rt| rt.iter_referenced_types().map(|s| s.to_string())),
)
}
pub fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
self.iter_resources()
.flat_map(|(_u, r)| r.iter_all_params())
.chain(
self.resource_types
.iter()
.flat_map(|rt| rt.iter_all_params()),
)
.chain(
self.representations
.iter()
.flat_map(|r| r.iter_all_params()),
)
}
}
impl std::str::FromStr for Application {
type Err = crate::parse::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::parse::parse_string(s)
}
}
#[derive(Debug)]
pub struct Resources {
pub base: Option<Url>,
pub resources: Vec<Resource>,
}
#[derive(Debug)]
pub struct Grammar {
pub href: RiReferenceString<IriSpec>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceTypeRef {
Id(Id),
Link(Url),
Empty,
}
impl std::str::FromStr for ResourceTypeRef {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"" => Ok(ResourceTypeRef::Empty),
s => {
if let Some(s) = s.strip_prefix('#') {
Ok(ResourceTypeRef::Id(s.to_string()))
} else {
Ok(ResourceTypeRef::Link(
s.parse().map_err(|e| format!("{}", e))?,
))
}
}
}
}
}
#[test]
fn parse_resource_type_ref() {
use crate::ast::ResourceTypeRef::*;
use std::str::FromStr;
assert_eq!(Empty, ResourceTypeRef::from_str("").unwrap());
assert_eq!(
Id("id".to_owned()),
ResourceTypeRef::from_str("#id").unwrap()
);
assert_eq!(
Link(Url::parse("https://example.com").unwrap()),
ResourceTypeRef::from_str("https://example.com").unwrap()
);
}
impl ResourceTypeRef {
pub fn id(&self) -> Option<&str> {
match self {
ResourceTypeRef::Id(id) => Some(id),
ResourceTypeRef::Link(l) => l.fragment(),
ResourceTypeRef::Empty => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct Options(BTreeMap<String, Option<mime::Mime>>);
impl Options {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, Option<&mime::Mime>)> {
self.0.iter().map(|(k, v)| (k.as_str(), v.as_ref()))
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.0.keys().map(|k| k.as_str())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn insert(&mut self, key: String, value: Option<mime::Mime>) {
self.0.insert(key, value);
}
pub fn get(&self, key: &str) -> Option<&Option<mime::Mime>> {
self.0.get(key)
}
}
impl From<Vec<String>> for Options {
fn from(v: Vec<String>) -> Self {
Self(v.into_iter().map(|s| (s, None)).collect())
}
}
impl From<Vec<&str>> for Options {
fn from(v: Vec<&str>) -> Self {
Self(v.into_iter().map(|s| (s.to_string(), None)).collect())
}
}
#[derive(Debug, Clone)]
pub struct Resource {
pub id: Option<Id>,
pub path: Option<String>,
pub r#type: Vec<ResourceTypeRef>,
pub query_type: mime::Mime,
pub methods: Vec<Method>,
pub docs: Vec<Doc>,
pub subresources: Vec<Resource>,
pub params: Vec<Param>,
}
impl Resource {
pub fn url(&self, base_url: Option<&Url>) -> Option<Url> {
self.path.as_ref().and_then(|path| {
if let Some(base_url) = base_url {
base_url.join(path).ok()
} else {
Url::parse(path).ok()
}
})
}
pub(crate) fn iter_all_params(&self) -> Box<dyn Iterator<Item = &Param> + '_> {
Box::new(
self.params
.iter()
.chain(self.subresources.iter().flat_map(|r| r.iter_all_params()))
.chain(self.methods.iter().flat_map(|m| m.iter_all_params())),
)
}
pub fn iter_referenced_types(&self) -> impl Iterator<Item = &str> + '_ {
self.iter_all_params().map(|p| p.r#type.as_str())
}
}
#[test]
fn test_resource_url() {
let r = Resource {
id: None,
path: Some("/foo".to_string()),
r#type: vec![],
query_type: mime::APPLICATION_JSON,
methods: vec![],
docs: vec![],
subresources: vec![],
params: vec![],
};
assert_eq!(
r.url(Some(&Url::parse("http://example.com").unwrap())),
Some(Url::parse("http://example.com/foo").unwrap())
);
assert_eq!(
r.url(Some(&Url::parse("http://example.com/bar").unwrap())),
Some(Url::parse("http://example.com/foo").unwrap())
);
}
#[derive(Debug, Clone)]
pub struct Method {
pub id: Id,
pub name: String,
pub docs: Vec<Doc>,
pub request: Request,
pub responses: Vec<Response>,
}
impl Method {
fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
self.request
.iter_all_params()
.chain(self.responses.iter().flat_map(|r| r.iter_all_params()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Doc {
pub title: Option<String>,
pub lang: Option<String>,
pub content: String,
pub xmlns: Option<url::Url>,
}
impl Doc {
pub fn new(content: String) -> Self {
Self {
content,
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Link {
pub resource_type: Option<ResourceTypeRef>,
pub relation: Option<String>,
pub reverse_relation: Option<String>,
pub doc: Option<Doc>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub style: ParamStyle,
pub id: Option<Id>,
pub name: String,
pub r#type: String,
pub path: Option<String>,
pub required: bool,
pub repeating: bool,
pub fixed: Option<String>,
pub doc: Option<Doc>,
pub links: Vec<Link>,
pub options: Option<Options>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RepresentationDef {
pub id: Option<Id>,
pub media_type: Option<mime::Mime>,
pub element: Option<String>,
pub profile: Option<String>,
pub docs: Vec<Doc>,
pub params: Vec<Param>,
}
impl RepresentationDef {
fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
self.params.iter()
}
}
#[derive(Debug, Clone)]
pub enum RepresentationRef {
Id(Id),
Link(Url),
}
impl RepresentationRef {
pub fn id(&self) -> Option<&str> {
match self {
RepresentationRef::Id(id) => Some(id),
RepresentationRef::Link(l) => l.fragment(),
}
}
}
#[derive(Debug, Clone)]
pub enum Representation {
Reference(RepresentationRef),
Definition(RepresentationDef),
}
impl Representation {
pub fn media_type(&self) -> Option<&mime::Mime> {
match self {
Representation::Reference(_) => None,
Representation::Definition(d) => d.media_type.as_ref(),
}
}
pub fn url(&self, base_url: &Url) -> Option<Url> {
match self {
Representation::Reference(RepresentationRef::Id(id)) => {
let mut url = base_url.clone();
url.set_fragment(Some(id));
Some(url)
}
Representation::Reference(RepresentationRef::Link(l)) => Some(l.clone()),
Representation::Definition(d) => d.url(base_url),
}
}
pub fn as_def(&self) -> Option<&RepresentationDef> {
match self {
Representation::Reference(_) => None,
Representation::Definition(d) => Some(d),
}
}
pub fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
let params = match self {
Representation::Reference(_) => vec![],
Representation::Definition(d) => d.iter_all_params().collect::<Vec<_>>(),
};
params.into_iter()
}
}
#[test]
fn test_representation_url() {
let base_url = Url::parse("http://example.com").unwrap();
let r = Representation::Reference(RepresentationRef::Id("foo".to_string()));
assert_eq!(
r.url(&base_url).unwrap(),
Url::parse("http://example.com#foo").unwrap()
);
let r = Representation::Reference(RepresentationRef::Link(
Url::parse("http://example.com#foo").unwrap(),
));
assert_eq!(
r.url(&base_url).unwrap(),
Url::parse("http://example.com#foo").unwrap()
);
let r = Representation::Definition(RepresentationDef {
id: Some("foo".to_string()),
..Default::default()
});
assert_eq!(
r.url(&base_url).unwrap(),
Url::parse("http://example.com#foo").unwrap()
);
}
#[test]
fn test_representation_id() {
let r = Representation::Reference(RepresentationRef::Id("foo".to_string()));
assert_eq!(r.as_def(), None);
let r = Representation::Definition(RepresentationDef {
id: Some("foo".to_string()),
..Default::default()
});
assert_eq!(r.as_def().unwrap().id, Some("foo".to_string()));
}
impl RepresentationDef {
pub fn url(&self, base_url: &Url) -> Option<Url> {
if let Some(id) = &self.id {
let mut url = base_url.clone();
url.set_fragment(Some(id));
Some(url)
} else {
None
}
}
}
#[derive(Debug, Default, Clone)]
pub struct Request {
pub docs: Vec<Doc>,
pub params: Vec<Param>,
pub representations: Vec<Representation>,
}
impl Request {
fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
self.params.iter().chain(
self.representations
.iter()
.filter_map(|r| r.as_def().map(|r| r.iter_all_params()))
.flatten(),
)
}
}
#[derive(Debug, Clone, Default)]
pub struct Response {
pub docs: Vec<Doc>,
pub params: Vec<Param>,
pub status: Option<i32>,
pub representations: Vec<Representation>,
}
impl Response {
fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
self.params.iter().chain(
self.representations
.iter()
.filter_map(|r| r.as_def().map(|r| r.iter_all_params()))
.flatten(),
)
}
}
#[derive(Debug)]
pub struct ResourceType {
pub id: Id,
pub query_type: mime::Mime,
pub methods: Vec<Method>,
pub docs: Vec<Doc>,
pub subresources: Vec<Resource>,
pub params: Vec<Param>,
}
impl ResourceType {
pub(crate) fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
self.params
.iter()
.chain(self.methods.iter().flat_map(|m| m.iter_all_params()))
}
pub fn iter_referenced_types(&self) -> impl Iterator<Item = &str> + '_ {
self.iter_all_params().map(|p| p.r#type.as_str())
}
}