use crate::deserialize::*;
#[cfg(feature = "json_schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
use struct_patch::Patch;
use url::Url;
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
#[serde(rename_all = "camelCase")]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Dofigen", rename = "Dofigen"))
    )
)]
pub struct Dofigen {
    #[patch(name = "VecPatch<String>")]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub context: Vec<String>,
    #[patch(name = "VecPatch<String>")]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ignores"))))]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub ignore: Vec<String>,
    #[patch(name = "HashMapDeepPatch<String, StagePatch>")]
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    pub builders: HashMap<String, Stage>,
    #[patch(name = "StagePatch", attribute(serde(flatten)))]
    #[serde(flatten)]
    pub stage: Stage,
    #[patch(name = "VecPatch<String>")]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub entrypoint: Vec<String>,
    #[patch(name = "VecPatch<String>")]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub cmd: Vec<String>,
    #[cfg_attr(
        feature = "permissive",
        patch(name = "VecDeepPatch<Port, ParsableStruct<PortPatch>>")
    )]
    #[cfg_attr(
        not(feature = "permissive"),
        patch(name = "VecDeepPatch<Port, PortPatch>")
    )]
    #[cfg_attr(
        not(feature = "strict"),
        patch(attribute(serde(alias = "port", alias = "ports")))
    )]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub expose: Vec<Port>,
    #[patch(name = "Option<HealthcheckPatch>")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub healthcheck: Option<Healthcheck>,
}
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(default)),
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Stage", rename = "Stage"))
    )
)]
pub struct Stage {
    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
    #[patch(name = "FromContextPatch", attribute(serde(flatten, default)))]
    pub from: FromContext,
    #[cfg_attr(
        feature = "permissive",
        patch(name = "Option<ParsableStruct<UserPatch>>")
    )]
    #[cfg_attr(not(feature = "permissive"), patch(name = "Option<UserPatch>"))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user: Option<User>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workdir: Option<String>,
    #[patch(name = "HashMapPatch<String, String>")]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "args"))))]
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    pub arg: HashMap<String, String>,
    #[patch(name = "HashMapPatch<String, String>")]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "envs"))))]
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    pub env: HashMap<String, String>,
    #[cfg_attr(
        not(feature = "strict"),
        patch(attribute(serde(
            alias = "add",
            alias = "adds",
            alias = "artifact",
            alias = "artifacts"
        )))
    )]
    #[cfg_attr(
        feature = "permissive",
        patch(name = "VecDeepPatch<CopyResource, ParsableStruct<CopyResourcePatch>>")
    )]
    #[cfg_attr(
        not(feature = "permissive"),
        patch(name = "VecDeepPatch<CopyResource, CopyResourcePatch>")
    )]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub copy: Vec<CopyResource>,
    #[patch(name = "Option<RunPatch>")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub root: Option<Run>,
    #[patch(name = "RunPatch", attribute(serde(flatten)))]
    #[serde(flatten)]
    pub run: Run,
}
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(default)),
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Run", rename = "Run"))
    )
)]
pub struct Run {
    #[patch(name = "VecPatch<String>")]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "script"))))]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub run: Vec<String>,
    #[cfg_attr(
        feature = "permissive",
        patch(name = "VecDeepPatch<Cache, ParsableStruct<CachePatch>>")
    )]
    #[cfg_attr(
        not(feature = "permissive"),
        patch(name = "VecDeepPatch<Cache, CachePatch>")
    )]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "caches"))))]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub cache: Vec<Cache>,
    #[cfg_attr(
        feature = "permissive",
        patch(name = "VecDeepPatch<Bind, ParsableStruct<BindPatch>>")
    )]
    #[cfg_attr(
        not(feature = "permissive"),
        patch(name = "VecDeepPatch<Bind, BindPatch>")
    )]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "binds"))))]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub bind: Vec<Bind>,
}
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Cache", rename = "Cache"))
    )
)]
pub struct Cache {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    pub target: String,
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "ro"))))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub readonly: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sharing: Option<CacheSharing>,
    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
    #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
    pub from: FromContext,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,
    #[cfg_attr(
        feature = "permissive",
        patch(attribute(serde(
            deserialize_with = "deserialize_from_optional_string_or_number",
            default
        )))
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chmod: Option<String>,
    #[patch(name = "Option<UserPatch>")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chown: Option<User>,
}
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Bind", rename = "Bind"))
    )
)]
pub struct Bind {
    pub target: String,
    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
    #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
    pub from: FromContext,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "rw"))))]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub readwrite: Option<bool>,
}
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Healthcheck", rename = "Healthcheck"))
    )
)]
pub struct Healthcheck {
    pub cmd: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub interval: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub timeout: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub retries: Option<u16>,
}
#[derive(Serialize, Debug, Clone, PartialEq, Default, Patch, Hash, Eq, PartialOrd)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "ImageName", rename = "ImageName"))
    )
)]
pub struct ImageName {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub host: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub port: Option<u16>,
    pub path: String,
    #[serde(flatten, skip_serializing_if = "Option::is_none")]
    #[patch(attribute(serde(flatten)))]
    pub version: Option<ImageVersion>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Copy", rename = "Copy"))
    )
)]
pub struct Copy {
    #[serde(flatten, skip_serializing_if = "FromContext::is_empty")]
    #[patch(name = "FromContextPatch", attribute(serde(flatten)))]
    pub from: FromContext,
    #[patch(name = "VecPatch<String>")]
    #[cfg_attr(
        not(feature = "strict"),
        patch(attribute(serde(alias = "path", alias = "source")))
    )]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub paths: Vec<String>,
    #[serde(flatten)]
    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
    pub options: CopyOptions,
    }
#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default, rename_all = "camelCase"))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "AddGitRepo", rename = "AddGitRepo"))
    )
)]
pub struct AddGitRepo {
    pub repo: String,
    #[serde(flatten)]
    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
    pub options: CopyOptions,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keep_git_dir: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Add", rename = "Add"))
    )
)]
pub struct Add {
    #[patch(name = "VecPatch<Resource>")]
    #[cfg_attr(not(feature = "strict"), patch(attribute(serde(alias = "file"))))]
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub files: Vec<Resource>,
    #[serde(flatten)]
    #[patch(name = "CopyOptionsPatch", attribute(serde(flatten)))]
    pub options: CopyOptions,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub checksum: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "CopyOptions", rename = "CopyOptions"))
    )
)]
pub struct CopyOptions {
    #[cfg_attr(
        not(feature = "strict"),
        patch(attribute(serde(alias = "destination")))
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target: Option<String>,
    #[patch(name = "Option<UserPatch>")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chown: Option<User>,
    #[cfg_attr(
        feature = "permissive",
        patch(attribute(serde(
            deserialize_with = "deserialize_from_optional_string_or_number",
            default
        )))
    )]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub chmod: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub link: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "User", rename = "User"))
    )
)]
pub struct User {
    pub user: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub group: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Patch)]
#[patch(
    attribute(derive(Deserialize, Debug, Clone, PartialEq, Default)),
    attribute(serde(deny_unknown_fields, default))
)]
#[cfg_attr(
    feature = "json_schema",
    patch(
        attribute(derive(JsonSchema)),
        attribute(schemars(title = "Port", rename = "Port"))
    )
)]
pub struct Port {
    pub port: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub protocol: Option<PortProtocol>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum ImageVersion {
    Tag(String),
    Digest(String),
}
#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum FromContext {
    FromImage(ImageName),
    FromBuilder(String),
    FromContext(Option<String>),
}
#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum CopyResource {
    Copy(Copy),
    AddGitRepo(AddGitRepo),
    Add(Add),
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum CacheSharing {
    Shared,
    Private,
    Locked,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum PortProtocol {
    Tcp,
    Udp,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Hash, Eq, PartialOrd)]
#[serde(untagged)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum Resource {
    Url(Url),
    File(PathBuf),
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum FromContextPatch {
    #[cfg(not(feature = "permissive"))]
    #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
    FromImage(ImageNamePatch),
    #[cfg(feature = "permissive")]
    #[cfg_attr(not(feature = "strict"), serde(alias = "image"))]
    FromImage(ParsableStruct<ImageNamePatch>),
    #[cfg_attr(not(feature = "strict"), serde(alias = "builder"))]
    FromBuilder(String),
    #[cfg_attr(not(feature = "strict"), serde(alias = "from"))]
    FromContext(Option<String>),
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(untagged)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub enum CopyResourcePatch {
    Copy(CopyPatch),
    AddGitRepo(AddGitRepoPatch),
    Add(AddPatch),
    Unknown(UnknownPatch),
}
#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
pub struct UnknownPatch {
    #[serde(flatten)]
    pub options: Option<CopyOptionsPatch>,
    }
#[cfg(test)]
mod test {
    use super::*;
    use pretty_assertions_sorted::assert_eq_sorted;
    mod deserialize {
        use super::*;
        mod dofigen {
            use super::*;
            #[test]
            fn empty() {
                let data = r#""#;
                let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
                let dofigen: Dofigen = dofigen.into();
                assert_eq_sorted!(dofigen, Dofigen::default());
            }
            #[test]
            fn from() {
                let data = r#"
                fromImage:
                  path: ubuntu
                "#;
                let dofigen: DofigenPatch = serde_yaml::from_str(data).unwrap();
                let dofigen: Dofigen = dofigen.into();
                assert_eq_sorted!(
                    dofigen,
                    Dofigen {
                        stage: Stage {
                            from: FromContext::FromImage(ImageName {
                                path: "ubuntu".into(),
                                ..Default::default()
                            }),
                            ..Default::default()
                        },
                        ..Default::default()
                    }
                );
            }
            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
            #[test]
            fn duplicate_from() {
                let data = r#"
                from:
                    path: ubuntu
                from:
                    path: alpine
                "#;
                let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
                println!("{:?}", dofigen);
                assert!(dofigen.is_err());
            }
            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
            #[test]
            fn duplicate_from_and_alias() {
                let data = r#"
                from:
                  path: ubuntu
                image:
                  path: alpine
                "#;
                let dofigen: serde_yaml::Result<DofigenPatch> = serde_yaml::from_str(data);
                println!("{:?}", dofigen);
                assert!(dofigen.is_err());
            }
        }
        mod stage {
            use super::*;
            #[test]
            fn empty() {
                let data = r#""#;
                let stage: StagePatch = serde_yaml::from_str(data).unwrap();
                let stage: Stage = stage.into();
                assert_eq_sorted!(stage, Stage::default());
            }
            #[test]
            fn from() {
                let data = r#"
                fromImage:
                  path: ubuntu
                "#;
                let stage: StagePatch = serde_yaml::from_str(data).unwrap();
                let stage: Stage = stage.into();
                assert_eq_sorted!(
                    stage,
                    Stage {
                        from: FromContext::FromImage(ImageName {
                            path: "ubuntu".into(),
                            ..Default::default()
                        })
                        .into(),
                        ..Default::default()
                    }
                );
            }
            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
            #[test]
            fn duplicate_from() {
                let data = r#"
                fromImage:
                    path: ubuntu
                fromImage:
                    path: alpine
                "#;
                let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
                assert!(stage.is_err());
            }
            #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
            #[test]
            fn duplicate_from_and_alias() {
                let data = r#"
                fromImage:
                  path: ubuntu
                image:
                  path: alpine
                "#;
                let stage: serde_yaml::Result<StagePatch> = serde_yaml::from_str(data);
                assert!(stage.is_err());
            }
        }
        mod user {
            use super::*;
            #[test]
            fn name_and_group() {
                let json_data = r#"{
    "user": "test",
    "group": "test"
}"#;
                let user: UserPatch = serde_yaml::from_str(json_data).unwrap();
                let user: User = user.into();
                assert_eq_sorted!(
                    user,
                    User {
                        user: "test".into(),
                        group: Some("test".into())
                    }
                );
            }
        }
        mod copy_resource {
            use super::*;
            #[test]
            fn copy() {
                let json_data = r#"{
    "paths": ["file1.txt", "file2.txt"],
    "target": "destination/",
    "chown": {
        "user": "root",
        "group": "root"
    },
    "chmod": "755",
    "link": true,
    "fromImage": {"path": "my-image"}
}"#;
                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
                let copy_resource: CopyResource = copy_resource.into();
                assert_eq_sorted!(
                    copy_resource,
                    CopyResource::Copy(Copy {
                        paths: vec!["file1.txt".into(), "file2.txt".into()].into(),
                        options: CopyOptions {
                            target: Some("destination/".into()),
                            chown: Some(User {
                                user: "root".into(),
                                group: Some("root".into())
                            }),
                            chmod: Some("755".into()),
                            link: Some(true),
                        },
                        from: FromContext::FromImage(ImageName {
                            path: "my-image".into(),
                            ..Default::default()
                        })
                    })
                );
            }
            #[test]
            fn copy_simple() {
                let json_data = r#"{
    "paths": ["file1.txt"]
}"#;
                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
                assert_eq_sorted!(
                    copy_resource,
                    CopyResourcePatch::Copy(CopyPatch {
                        paths: Some(vec!["file1.txt".into()].into_patch()),
                        options: Some(CopyOptionsPatch::default()),
                        ..Default::default()
                    })
                );
                let copy_resource: CopyResource = copy_resource.into();
                assert_eq_sorted!(
                    copy_resource,
                    CopyResource::Copy(Copy {
                        paths: vec!["file1.txt".into()].into(),
                        options: CopyOptions::default(),
                        ..Default::default()
                    })
                );
            }
            #[cfg(feature = "permissive")]
            #[test]
            fn copy_chmod_int() {
                let json_data = r#"{
    "paths": ["file1.txt"],
    "chmod": 755
}"#;
                let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();
                assert_eq_sorted!(
                    copy_resource,
                    CopyPatch {
                        paths: Some(vec!["file1.txt".into()].into_patch()),
                        options: Some(CopyOptionsPatch {
                            chmod: Some(Some("755".into())),
                            ..Default::default()
                        }),
                        ..Default::default()
                    }
                );
            }
            #[cfg(feature = "permissive")]
            #[test]
            fn deserialize_copy_from_str() {
                let json_data = "file1.txt destination/";
                let copy_resource: ParsableStruct<CopyResourcePatch> =
                    serde_yaml::from_str(json_data).unwrap();
                let copy_resource: CopyResource = copy_resource.into();
                assert_eq_sorted!(
                    copy_resource,
                    CopyResource::Copy(Copy {
                        paths: vec!["file1.txt".into()].into(),
                        options: CopyOptions {
                            target: Some("destination/".into()),
                            ..Default::default()
                        },
                        ..Default::default()
                    })
                );
            }
            #[test]
            fn add_git_repo() {
                let json_data = r#"{
            "repo": "https://github.com/example/repo.git",
            "target": "destination/",
            "chown": {
                "user": "root",
                "group": "root"
            },
            "chmod": "755",
            "link": true,
            "keepGitDir": true
        }"#;
                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
                let copy_resource: CopyResource = copy_resource.into();
                assert_eq_sorted!(
                    copy_resource,
                    CopyResource::AddGitRepo(AddGitRepo {
                        repo: "https://github.com/example/repo.git".into(),
                        options: CopyOptions {
                            target: Some("destination/".into()),
                            chown: Some(User {
                                user: "root".into(),
                                group: Some("root".into())
                            }),
                            chmod: Some("755".into()),
                            link: Some(true),
                        },
                        keep_git_dir: Some(true)
                    })
                );
            }
            #[test]
            fn add() {
                let json_data = r#"{
            "files": ["file1.txt", "file2.txt"],
            "target": "destination/",
            "checksum": "sha256:abcdef123456",
            "chown": {
                "user": "root",
                "group": "root"
            },
            "chmod": "755",
            "link": true
        }"#;
                let copy_resource: CopyResourcePatch = serde_yaml::from_str(json_data).unwrap();
                let copy_resource: CopyResource = copy_resource.into();
                assert_eq_sorted!(
                    copy_resource,
                    CopyResource::Add(Add {
                        files: vec![
                            Resource::File("file1.txt".into()),
                            Resource::File("file2.txt".into())
                        ]
                        .into(),
                        options: CopyOptions {
                            target: Some("destination/".into()),
                            chown: Some(User {
                                user: "root".into(),
                                group: Some("root".into())
                            }),
                            chmod: Some("755".into()),
                            link: Some(true),
                        },
                        checksum: Some("sha256:abcdef123456".into()),
                    })
                );
            }
        }
        mod builder {
            use super::*;
            #[test]
            fn with_bind() {
                let json_data = r#"
fromImage:
  path: clux/muslrust:stable
workdir: /app
bind:
  - target: /app
run:
  - cargo build --release
  - mv target/x86_64-unknown-linux-musl/release/dofigen /app/
"#;
                let builder: Stage = serde_yaml::from_str::<StagePatch>(json_data)
                    .unwrap()
                    .into();
                assert_eq_sorted!(
                    builder,
                    Stage {
                        from: FromContext::FromImage(ImageName {
                            path: "clux/muslrust:stable".into(),
                            ..Default::default()
                        }),
                        workdir: Some("/app".into()),
                        run: Run {
                            bind: vec![Bind {
                                target: "/app".into(),
                                ..Default::default()
                            }],
                            run: vec![
                                "cargo build --release".into(),
                                "mv target/x86_64-unknown-linux-musl/release/dofigen /app/".into()
                            ],
                            ..Default::default()
                        },
                        ..Default::default()
                    }
                );
            }
        }
    }
}